using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;

using Atomic.Interfaces.Atat;
using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;
using Atomic.Structures.Symmetries;

namespace Atomic.Clusters
{
	[Serializable]
	public class ClusterFinder
	{
		public ClusterFinder(Structure lattice)
		{
			CheckLattice(lattice);

			Lattice = lattice;
		}

		private static void CheckLattice(Structure lattice)
		{
			foreach (Site site in lattice.Sites)
			{
				if (site.Atom is PlaceholderAtom)
				{
					return;
				}
			}

			throw new ArgumentException("Need at least one site with a placeholder in the lattice.");
		}

		public MappedStructure MapStructure(Structure structure)
		{
			return MappedStructure.MapStructure(Lattice, structure);
		}

		/*public double MapClusterValue(Structure structure, ClusterClass cluster)
		{
			return new List<double>(MapClusterValues(structure, new ClusterClass[] { cluster }))[0];
		}

		public IEnumerable<double> MapClusterValues(Structure structure, IEnumerable<ClusterClass> clusters)
		{
			return new ClusterEvaluator(Lattice, structure).ClusterValues(clusters);
		}

		public IEnumerable<double> MapClusterValues(Structure structure, params ClusterClass[] clusters)
		{
			return new List<double>(MapClusterValues(structure, (IEnumerable<ClusterClass>)clusters)).ToArray();
		}*/

		public IEnumerable<ClusterClass> Find(double d)
		{
			// Perform sorting?
			return Find(d, d, d, d, d);
		}

		public IEnumerable<ClusterClass> Find(double d, int n)
		{
			if (d < 0.0)
			{
				throw new ArgumentException();
			}

			IEnumerable<ClusterClass> clusters;
			switch (n)
			{
				case 0:
				case 1:
					// Don't care about maximum distance for the empty and single point clusters.
					clusters = Find(0.0, 0.0, 0.0, 0.0, 0.0);
					break;

				case 2:
					clusters = Find(d, 0.0, 0.0, 0.0, 0.0);
					break;

				case 3:
					clusters = Find(0.0, d, 0.0, 0.0, 0.0);
					break;

				case 4:
					clusters = Find(0.0, 0.0, d, 0.0, 0.0);
					break;

				case 5:
					clusters = Find(0.0, 0.0, 0.0, d, 0.0);
					break;

				case 6:
					clusters = Find(0.0, 0.0, 0.0, 0.0, d);
					break;

				default:
					throw new NotSupportedException("Clusters with up to 6 points are supported only.");
			}

			// Filter out the empty and single point clusters unless requested.
			return new List<ClusterClass>(clusters).FindAll(cluster => cluster.Points.Count == n).ToArray();
		}

		public IEnumerable<ClusterClass> Find(double d2, double d3, double d4, double d5, double d6)
		{
			if (d2 < 0.0 || d3 < 0.0 || d4 < 0.0 || d5 < 0.0 || d6 < 0.0)
			{
				throw new ArgumentException();
			}

			// Avoid exclusion of clusters due to round-off errors.
			double delta = 1.0e-8;

			using (TempDirectory temp = new TempDirectory())
			{
				Lattice.Write(temp.Directory.AddFile("lat.in"));

				// Start corrdump process. Find cluster and symmetries only. IEEE 754 double-precision has up to 17 significant digits.
				Process p = new Process();
				p.StartInfo.RedirectStandardOutput = true;
				p.StartInfo.RedirectStandardError = true;
				p.StartInfo.UseShellExecute = false;
				p.StartInfo.FileName = "corrdump";
				p.StartInfo.Arguments = string.Format(CultureInfo.InvariantCulture, "-clus -sig=17 -2={0:r} -3={1:r} -4={2:r} -5={3:r} -6={4:r}",
					Math.Max(0.001, d2 + delta),
					d3 != 0.0 ? d3 + delta : 0.0,
					d4 != 0.0 ? d4 + delta : 0.0,
					d5 != 0.0 ? d5 + delta : 0.0,
					d6 != 0.0 ? d6 + delta : 0.0);
				p.StartInfo.WorkingDirectory = temp.Directory.FullName;
				p.Start();
				p.WaitForExit();

				// Symmetries of the lattice is used to determine equivalence class of clusters.
				return Read(temp.Directory.AddFile("clusters.out"), AtatSymmetryOperationParser.FindSymmetries(temp.Directory));
			}

			// Perform sorting?
		}

		public IEnumerable<ClusterClass> Read(FileInfo file)
		{
			// Ignore symmetries.
			return Read(file, null);
		}

		private IEnumerable<ClusterClass> Read(FileInfo file, IEnumerable<SpaceGroupSymmetryOperation> symmetries)
		{
			List<ClusterClass> clusters = new List<ClusterClass>();

			List<string> lines = new List<string>(File.ReadLines(file.FullName));
			while (lines.Count > 0)
			{
				List<ClusterPoint> points = new List<ClusterPoint>();

				int m = int.Parse(lines[0]);
				lines.RemoveAt(0);

				double d = double.Parse(lines[0], CultureInfo.InvariantCulture);
				lines.RemoveAt(0);

				int n = int.Parse(lines[0]);
				lines.RemoveAt(0);

				for (int i = 0; i < n; i++)
				{
					string[] cols = lines[0].Split(' ');
					SpaceVector position = AtomicEnvironment.ParseSpaceVector(cols[0], cols[1], cols[2]);
					lines.RemoveAt(0);

					Site site = Identify(position);

					if (!(site.Atom is PlaceholderAtom && int.Parse(cols[3]) == ((PlaceholderAtom)site.Atom).Atoms.Count - 2))
					{
						throw new NotSupportedException();
					}
					ClusterFunctionFactor function = new ClusterFunctionFactor(((PlaceholderAtom)site.Atom).Atoms, int.Parse(cols[4]));

					points.Add(new ClusterPoint(site, position, function));
				}

				lines.RemoveAt(0);

				clusters.Add(new ClusterClass(Lattice, points, d, m, symmetries));
				//clusters.Add(new Cluster(points, Lattice.SpaceGroup.Symmetries, d, m, Lattice));
			}

			return clusters;
		}

		public void Write(FileInfo file, IEnumerable<ClusterClass> clusters)
		{
			using (StreamWriter writer = new StreamWriter(file.FullName))
			{
				foreach (ClusterClass cluster in clusters)
				{
					writer.WriteLine(cluster.Multiplicity);
					writer.WriteLine(cluster.MaxDistance.ToString("r", CultureInfo.InvariantCulture));
					writer.WriteLine(cluster.Points.Count.ToString());
					foreach (ClusterPoint point in cluster.Points)
					{
						writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r} {3} {4}", point.Position.X, point.Position.Y, point.Position.Z, point.ClusterFunction.Atoms.Count - 2, point.ClusterFunction.Index));
					}
					writer.WriteLine();
				}
				writer.Close();
			}
		}

		private Site Identify(SpaceVector position)
		{
			// Only IEEE round-off errors expected in this identification.
			double tol = 1.0e-6;

			Lattice lattice = Lattice.Lattice;

			foreach (Site site in Lattice.Sites)
			{
				// Try solving for integers n1, n2, and n3.
				// position == site.Position + n1 * structure.Lattice.A + ...

				// The position given is assumed to be relative to the lattice. 
				SpaceVector n = position - lattice.Decompose(site.Position).Coordinates;

				// If this is the same site, d has to be integers only. Check the rounded off values in the physical coordinates.
				if (SpaceVector.Norm(lattice.PhysicalMap * new SpaceVector(n[0] - Math.Round(n[0]), n[1] - Math.Round(n[1]), n[2] - Math.Round(n[2]))) < tol)
				{
					return site;
				}
			}

			throw new Exception("Couldn't identify site positions.");
		}

		public Structure Lattice
		{
			get;
			private set;
		}

		/*private class ClusterEvaluator
		{
			public ClusterEvaluator(Structure lattice, Structure structure)
			{
				Lattice = lattice;
				Structure = structure;
			}

			public IEnumerable<double> ClusterValues(IEnumerable<ClusterClass> clusters)
			{
				string s;
	
				using (TempDirectory temp = new TempDirectory())
				{
					Lattice.Write(new FileInfo(Path.Combine(temp.Directory.FullName, "lat.in")));
					Structure.Write(new FileInfo(Path.Combine(temp.Directory.FullName, "str.out")));
					new ClusterFinder(Lattice).Write(temp.Directory.AddFile("clusters.out"), clusters); // FIXME
					//WriteCluster(clusters, new FileInfo(Path.Combine(temp.Directory.FullName, "clusters.out")));

					// Start corrdump process. Use predefined clusters. IEEE 754 double-precision has up to 17 significant digits.
					Process p = new Process();
					try
					{
						p.StartInfo.RedirectStandardOutput = true;
						p.StartInfo.RedirectStandardError = true;
						p.StartInfo.UseShellExecute = false;
						p.StartInfo.FileName = "corrdump";
						p.StartInfo.Arguments = "-c -sig=17";
						p.StartInfo.WorkingDirectory = temp.Directory.FullName;
						p.Start();
						p.WaitForExit();
						s = p.StandardOutput.ReadToEnd();
					}
					finally
					{
						if (!p.HasExited)
						{
							p.Kill();
						}
					}
				}

				return AtomicEnvironment.ParseVector(s.Replace("\t", " ").Replace("\n", " ")).ToArray();
			}

			public Structure Lattice
			{
				get;
				private set;
			}

			public Structure Structure
			{
				get;
				private set;
			}
		}*/
	}
}
